/**
* @file pic.cpp
* Programmable Interrupt Controller interface for X86 platform.
* Contains data structures and function for PIC managment:
* to send commands to PIC, to send/read data to/from PIC, to init PIC, 
* Inicialization Control Words constants etc...
*
* Copyrights 2010 Michal Saman, m.saman@designplus.cz
* This source code is release under the Apache License 2.0.
*/
#ifndef ARCH_X86
#error "[pic.cpp] is platform-independent. Define ARCH_X86."
#endif

#include <stdint.h>
#include <hal/hal.h>
#include <hal/pic.h>

/**
* PIC 1 hardware port address.
*/
#define PIC1_REG_COMMAND	0x20
#define PIC1_REG_STATUS		0x20
#define	PIC1_REG_DATA		0x21
#define	PIC1_REG_IMR		0x21

/**
* PIC 2 hardware port address.
*/
#define PIC2_REG_COMMAND	0xA0
#define PIC2_REG_STATUS		0xA0
#define	PIC2_REG_DATA		0xA1
#define	PIC2_REG_IMR		0xA1

/**
* Helper constants used as command words for inicialize PIC.
*/
// Inicialization Control Word 1 bit masks
#define PIC_ICW1_MASK_IC4		0x1		// 00000001
#define PIC_ICW1_MASK_SNGL		0x2		// 00000010
#define PIC_ICW1_MASK_ADI		0x4		// 00000100
#define PIC_ICW1_MASK_LTIM		0x8		// 00001000
#define PIC_ICW1_MASK_INIT		0x10	// 00010000
// Inicialization Control Word 4 bit masks
#define PIC_ICW4_MASK_UPM		0x1		// 00000001
#define PIC_ICW4_MASK_AEOI		0x2		// 00000010
#define PIC_ICW4_MASK_MS		0x4		// 00000100
#define PIC_ICW4_MASK_BUF		0x8		// 00001000
#define PIC_ICW4_MASK_SFNM		0x10	// 00010000

/**
* Inicialization Command 1 control bits.
*/
#define PIC_ICW1_IC4_EXPECT				1	// 1
#define PIC_ICW1_IC4_NO					0	// 0

#define PIC_ICW1_SNGL_YES				2	// 10
#define PIC_ICW1_SNGL_NO				0	// 00

#define PIC_ICW1_ADI_CALLINTERVAL4		4	// 100
#define PIC_ICW1_ADI_CALLINTERVAL8		0	// 000

#define PIC_ICW1_LTIM_LEVELTRIGGERED	8	// 1000
#define PIC_ICW1_LTIM_EDGETRIGGERED		0	// 0000

#define PIC_ICW1_INIT_YES				0x10	// 10000
#define PIC_ICW1_INIT_NO				0		// 00000

/**
* Inicialization Command 4 control bits.
*/
#define PIC_ICW4_UPM_86MODE			1	// 1
#define PIC_ICW4_UPM_MCSMODE		0	// 0

#define PIC_ICW4_AEOI_AUTOEOI		2	// 10
#define PIC_ICW4_AEOI_NOAUTOEOI		0	// 0

#define	PIC_ICW4_MS_BUFFERMASTER	4	// 100
#define PIC_ICW4_MS_BUFFERSLAVE		0	// 0

#define PIC_ICW4_BUF_MODEYES		8	// 1000
#define PIC_ICW4_BUF_MODENO			0   // 0

#define PIC_ICW4_SFNM_NESTEDMODE	0x10	// 10000
#define PIC_ICW4_SFNM_NOTNESTED		0		// 0

/**
* Function send command register to specified PIC.
* @param pic		Command register (see 
* @param pic_num	Number of PIC (1, 2)
*/
extern void pic_send_command(uint8_t cmd, uint8_t pic_num) {
	if (pic_num > 1)
		return;
	uint8_t reg = (pic_num==1) ? PIC2_REG_COMMAND : PIC1_REG_COMMAND;
	outportb(reg, cmd);
}

/**
* Function send data to specified PIC.
* @param data		Data send to PIC
* @param pic_num	Number of PIC (1, 2)
*/
extern void pic_send_data(uint8_t data, uint8_t pic_num) {
	if (pic_num>1)
		return;
	uint8_t reg = (pic_num==1) ? PIC2_REG_DATA : PIC1_REG_DATA;
	outportb(reg, data);
}

/**
* Function read data from specified PIC.
* @param	pic_num		Number of PIC (1, 2)
* @return	data		Data readed from PIC
*/
extern uint8_t pic_read_data(uint8_t pic_num) {
	if (pic_num>1)
		return 0;
	uint8_t reg = (pic_num==1) ? PIC2_REG_DATA : PIC2_REG_DATA;
	inportb(reg);
	return reg;
}

/**
* Inicialize Programable Interrupt Controrels
* @param base0			Base IRQ number of IDT for PIC1
* @param base1			Base IRQ number of IDT for PIC2
*/
extern void pic_init(uint8_t base0, uint8_t base1) {
	// disable HW interrupts
	// disable_hw_interrupts();
	
	// inicialization Control word 1
	uint8_t	icw = 0;
	
	icw = (icw & ~PIC_ICW1_MASK_INIT) | PIC_ICW1_INIT_YES;
	icw = (icw & ~PIC_ICW1_MASK_IC4) | PIC_ICW1_IC4_EXPECT;

	// send inicialization Control word 1 to both PICs
	pic_send_command(icw, 0);	// PIC1
	pic_send_command(icw, 1);	// PIC2

	// send inicialization control word 2 to both PICs
	// In 80x86 mode, specifies the interrupt vector address.
	pic_send_data(base0, 0);	// PIC1
	pic_send_data(base1, 1);	// PIC2

	// send inicialization control word 3 to both PICs - this allows us to set which IRQ to use to communicate with each other (cascade mode).
	// For master PIC is the IR that connects to secondary PIC in binary format - each bit represents an interrupt request
	// For slave PIC is the IR that connects to master pic in decimal format
	// The 80x86 architecture uses IRQ line 2 to connect the master PIC to the slave PIC.
	pic_send_data(0x04, 0); // 100 - it,s IR line 2
	pic_send_data(0x02, 1); // 10  - it's IR line 2 (binary to decimal)

	// send inicialization control word 4 - enables X86 mode
	//icw = 0;
	icw = (icw & ~PIC_ICW4_MASK_UPM) | PIC_ICW4_UPM_86MODE;
	pic_send_data(icw, 0);
	pic_send_data(icw, 1);
}
